package Game;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import Game.MapBlock.EMapBlockType;
import GameMath.GPoint;
import GameMath.GSize;
import Tools.Pathfinding.Pathfinding;

/** Level map class
 * @author Aikidos
 */
public class LevelMap {
	/** Map */
	private MapBlock[][] _map;
	
	/** Map size */
	private GSize _mapSize;
	
	/** Number of rooms */
	private int _numOfRooms;
	
	/** Returns map blocks
	 * @return Map blocks
	 */
	public MapBlock[][] getBlocks() {
		return _map;
	}
	
	/** Returns map size
	 * @return Map size
	 */
	public GSize getSize() {
		return _mapSize;
	}
	
	/** Returns number of rooms
	 * @return Number of rooms
	 */
	public int getNumOfRooms() {
		return _numOfRooms;
	}

	/** Returns distance between coordinates
	 * (Uses pathfinding!)
	 * @param start - Start point
	 * @param finish - Finish point
	 * @return Distance (if (distance = -1) - ERROR distance calculation)
	 */
	public int getDistance(GPoint start, GPoint finish) {
		List<GPoint> path = Pathfinding.find(start, finish);
		
		return (path == null) ? -1 : path.size() - 1;
	}
	
	/** Set number of rooms
	 * @param numOfRooms - Number of rooms
	 */
	public void setNumOfRooms(int numOfRooms) {
		_numOfRooms = numOfRooms;
	}
	
	/** Returns list of coordinate place by template
	 * @param template - Template
	 * @param templateSize - Template size
	 * @return List of coordinate place 
	 */
	public List<GPoint> getPlacesByTemplate(EMapBlockType[][] template, GSize templateSize) {
		
		List<GPoint> ret = new ArrayList<GPoint>();
		
		for (int i = 0; i < _mapSize.width; i++) {
			for (int j = 0; j < _mapSize.height; j++) {
				
				
				// If coincidence detected
				if (_map[i][j].Type == template[0][0]) {
					// Scan full coincidence
					boolean equal = true;
					for (int x = 0; x < templateSize.width; x++) {
						for (int y = 0; y < templateSize.height; y++) {
							
							int xi = x + i;
							int yj = y + j;
							
							// Scan point is valid?
							if ((xi >= 0 && xi < _mapSize.width) 
									&& (yj >= 0 && yj < _mapSize.height)) {
								if (_map[xi][yj].Type != template[x][y]) equal = false;
							}
							else 
								equal = false;
							
							if (!equal) break;
						}
						
						if (!equal) break;
					}
					
					if (equal) {
						ret.add(new GPoint(i, j));
					}
					
				}
				
				
			}
		}
		
		return ret;
	}
	
	/** Open map */
	public void openMap() {
		for (int i = 0; i < _mapSize.width; i++) {
			for (int j = 0; j < _mapSize.height; j++) {
				_map[i][j].IsOpen = true;
			}
		}
	}
	
	/** Create empty map
	 * @param size - Map size
	 */
	public void createEmptyMap(GSize size) {
		_mapSize = size;
		
		_map = new MapBlock[size.width][size.height];
		for (int i = 0; i < size.width; i++) {
			for (int j = 0; j < size.height; j++) {
				_map[i][j] = new MapBlock();
				_map[i][j].Type = EMapBlockType.None;
				_map[i][j].IsOpen = false;
			}
		}
	}
	
	/** Generation level (called after 'createEmptyMap' method) */
	public void generation() {
		// Create list of room positions
		List<GPoint> lstRooms = new ArrayList<GPoint>();

		// Create rooms
		Random rnd = GameHelper.getRandom();
		int numOfRooms = _numOfRooms;

		for (int i = 0; i < numOfRooms; i++) {
			// Set radius
			int roomRadius = rnd.nextInt(5) + 2;

			// Set position
			GPoint pos = new GPoint(rnd.nextInt(_mapSize.width), rnd.nextInt(_mapSize.height));
			while (((pos.x - roomRadius) <= 0)
					|| ((pos.x + roomRadius) >= (_mapSize.width - 1))
					|| ((pos.y - roomRadius) <= 0)
					|| ((pos.y + roomRadius) >= (_mapSize.height - 1))) {
				pos = new GPoint(rnd.nextInt(_mapSize.width), rnd.nextInt(_mapSize.height));
			}

			// Create room
			for (int x = pos.x - roomRadius; x < pos.x + roomRadius; x++)
				for (int y = pos.y - roomRadius; y < pos.y + roomRadius; y++)
					_map[x][y].Type = EMapBlockType.Floor;

			// Add room
			lstRooms.add(pos);
		}

		// Create roads
		int DIR_LEFT = 0;
		int DIR_TOP = 1;
		int DIR_RIGHT = 2;
		int DIR_BOTTOM = 3;

		for (int i = 0; i < lstRooms.size(); i++) {
			GPoint roomPos = lstRooms.get(i);
			GPoint drawRoadPos = new GPoint(roomPos);

			// Get next room position
			GPoint nextRoomPos = new GPoint();
			if ((i + 1) < lstRooms.size())
				nextRoomPos = lstRooms.get(i + 1);
			else
				break;

			// Draw road
			while ((drawRoadPos.x != nextRoomPos.x)
					|| (drawRoadPos.y != nextRoomPos.y)) {
				// Get available directions
				List<Integer> lstDirs = new ArrayList<Integer>();

				if (drawRoadPos.x < nextRoomPos.x)
					lstDirs.add(DIR_RIGHT);
				else if (drawRoadPos.x > nextRoomPos.x)
					lstDirs.add(DIR_LEFT);

				if (drawRoadPos.y < nextRoomPos.y)
					lstDirs.add(DIR_BOTTOM);
				else if (drawRoadPos.y > nextRoomPos.y)
					lstDirs.add(DIR_TOP);

				// Select random direction
				if (lstDirs.size() != 0) {
					switch (lstDirs.get(rnd.nextInt(lstDirs.size()))) {
					// Left
					case 0:
						drawRoadPos.x -= 1;
						break;

					// Top
					case 1:
						drawRoadPos.y -= 1;
						break;

					// Right
					case 2:
						drawRoadPos.x += 1;
						break;

					// Bottom
					case 3:
						drawRoadPos.y += 1;
						break;
					}

					// Set block as floor
					_map[drawRoadPos.x][drawRoadPos.y].Type = EMapBlockType.Floor;
				}
			}
		}

		// Create walls
		for (int i = 0; i < _mapSize.width; i++) {
			for (int j = 0; j < _mapSize.height; j++) {
				if (_map[i][j].Type == EMapBlockType.Floor) {
					// Left
					if (_map[i - 1][j].Type == EMapBlockType.None)
						_map[i - 1][j].Type = EMapBlockType.Wall;

					// Top
					if (_map[i][j - 1].Type == EMapBlockType.None)
						_map[i][j - 1].Type = EMapBlockType.Wall;

					// Right
					if (_map[i + 1][j].Type == EMapBlockType.None)
						_map[i + 1][j].Type = EMapBlockType.Wall;

					// Bottom
					if (_map[i][j + 1].Type == EMapBlockType.None)
						_map[i][j + 1].Type = EMapBlockType.Wall;
				}
			}
		}
		
		// Create new pathfinding map
		Pathfinding.createNewPathfindingMap(_map, _mapSize);
	}
}
